/*
 * Copyright (C) 2011 ST-Ericsson AS
 * Author: Magnus Reftel / magnus.xm.reftel@stericsson.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 */

#include <mid_mfa.h>

#include <sys/socket.h>
#include <sys/un.h>

#ifdef HAVE_ANDROID_OS
#include "cutils/properties.h"
#else
#include <stdlib.h>
#endif /* HAVE_ANDROID_OS */

#include <mid_conf.h>
#include <mid_statemachine.h>

#include <apr_poll.h>
#include <apr_file_io.h>
#include <apr_portable.h>

/* Begin copy from ControlCommands.h */

typedef enum {
    SET_TARGET_SVCMODE = 0x01,  /* Perform needed HW setup for target to boot in service mode  */
    SHUTDOWN_RELAY,             /* Release used resources and shutdown the communication relay */
    CHANGE_TARGET_BAUDRATE,     /* Change the baudrate of the target device (valid only for UART) */
    CHANGE_HOST_BAUDRATE,       /* Change the baudrate of the host device (valid only for UART) */
    SWITCH_TARGET_DEVICE,       /* Change the communication device that is used by target */
    SWITCH_HOST_DEVICE,         /* Change the communication device that is used by host */
    CHANGE_TRSNSFER_LENGTH,     /* Start using another length of data transfers */
    TURNOFF_TARGET_HARDWARE,    /* Terminate target interface hardware */
    MFA_MID_SYNC,               /* Synchronize MFA and MID. */
    MAX_CMD_NUMBER
} LoaderRelayCommand_t;

/* From ControlCommands.h */
typedef enum {
    MFA_IS_READY = 0x01,        /* Tells to MID that MFA is ready. */
    MID_ENABLED_TX,             /* MID tells to MFA that TX is enabled. */
    MFA_HAS_FINISHED,           /* MFA tells to MID that interaction with the modem has finished. */
} MfaMidSyncMessage_t;

typedef enum {
    MM_SUCCESS = 0x00,          /* Successfull execution. */
    MM_FAILED  = 0x01,          /* General fatal error. */
} MfaMidSyncError_t;

/* End of copy from ControlCommands.h */

struct mid_mfa
{
	apr_socket_t* socket;
#ifdef HAVE_ANDROID_OS
	char service_name[PROPERTY_VALUE_MAX];
#else
	char start_command[100];
	char stop_command[100];
#endif
	char socket_path[sizeof(((struct sockaddr_un*)0)->sun_path)];
	struct mid* mid_info;
	apr_pool_t* pool;
	bool is_flashing;
	bool was_successful;
	bool waiting_for_ack;
};

struct mid_mfa* mid_mfa_init(struct mid* mid_info, const struct mid_config* config, apr_pool_t* pool)
{
	apr_pool_t* subpool;
	struct mid_mfa* context;
	apr_status_t status;

	MLGD("MFA: Initializing MFA module.");

	if ((status = apr_pool_create(&subpool, pool)) != APR_SUCCESS) {
		char message[100];
		MLGE("MFA: Failed to create pool for MFA module: %s", apr_strerror(status, message, sizeof(message)));
		goto error_pool;
	}
	context = apr_palloc(subpool, sizeof(*context));
	context->socket = NULL;
#ifdef HAVE_ANDROID_OS
	strncpy(context->service_name, config->mfa_service_name, sizeof(context->service_name));
	MLGD("MFA: Using android service \"%s\"", context->service_name);
#else
	strncpy(context->start_command, config->mfa_start_command, sizeof(context->start_command));
	strncpy(context->stop_command, config->mfa_stop_command, sizeof(context->stop_command));
#endif
	strncpy(context->socket_path, config->mfa_control_path, sizeof(context->socket_path));
	MLGD("MFA: Path to MFA control socket: %s", context->socket_path);
	context->pool = subpool;
	context->mid_info = mid_info;
	context->is_flashing = false;
	context->was_successful = false;
	return context;
error_pool:
	return NULL;
}

static void hexdump(const char* message, const uint8_t* data, size_t length)
{
	size_t i;
	char s[2*length];
	char* t = s;
	for(i=0; i<length; i++)
		t+=sprintf(t, "%02x", data[i]);
	MLGD("%s: %s", message, s);
}

static bool mid_mfa_send_message(struct mid_mfa* context, const uint8_t* message, apr_size_t len)
{
	apr_status_t status;

	hexdump("MFA: Sending message", message, len);

	if ((status = apr_socket_send(context->socket, (char*)message, &len)) != APR_SUCCESS) {
		char error_message[100];
		MLGE("MFA: Failed to send message to MFA: %s", apr_strerror(status, error_message, sizeof(error_message)));
		return false;
	}
	return true;
}

void mid_mfa_receive_message(struct apr_pollfd_t* pollfd, struct mid* mid_info, struct mid_mfa* context)
{
	uint8_t message[4];
	apr_size_t len;
	apr_status_t status;
	static const uint8_t gr_ok[]={0,0,0,0};
	static const uint8_t gr_fail[]={1,0,0,0};

	UNUSED(mid_info);

	MLGD("MFA: Received events from MFA socket: %08x", pollfd->rtnevents);

	if (! (pollfd->rtnevents & APR_POLLIN)) {
		MLGD("MFA: Ignoring event from MFA socket");
		return;
	}

	len = sizeof(message);
	if ((status = apr_socket_recv(pollfd->desc.s, (char*)message, &len)) != APR_SUCCESS) {
		char error_message[100];
		MLGE("MFA: Failed to read message from the MFA: %s",
			apr_strerror(status, error_message, sizeof(error_message)));
		if (status == APR_EOF) {
			MLGE("MFA: MFA socket has been closed. Aborting flashing.");
			mid_mfa_stop(context, false);
		}
		goto error_read;
	}
	hexdump("MFA: Received message", message, len);
	if (len != sizeof(message)) {
		apr_size_t i;
		MLGE("MFA: Got invalid message from the MFA (%zd bytes)", len);
		for(i=0; i<len; i++) MLGD("MFA: byte[%02zd]: %02x", i, message[i]);
		goto error_invalid;
	}

	if (context->waiting_for_ack) {
		if (memcmp(message, gr_ok, sizeof(gr_ok)) == 0) {
			MLGD("Got ack from MFA");
			context->waiting_for_ack = false;
			return;
		} else if (memcmp(message, gr_fail, sizeof(gr_fail)) == 0) {
			MLGD("Got nack from MFA");
			context->waiting_for_ack = false;
			mid_mfa_stop(context, false);
			SET_FLAG(mid_info->event, MID_IPC_RBT_EVT);
			return;
		} else {
			MLGW("Got unexpected message when waiting for General Response: %02x", message[0]);
			goto error_unrecognized;
		}
	}

	if (message[0] != MFA_MID_SYNC) {
		MLGW("MFA: Got non-MID/MFA message from the MFA: %02x", message[0]);
		goto error_unrecognized;
	}

	if (message[3] != MM_SUCCESS) {
		MLGW("MFA: Got error message from the MFA");
		mid_mfa_stop(context, false);
		SET_FLAG(mid_info->event, MID_IPC_RBT_EVT);
		goto error_nack;
	}

	switch(message[1]) {
	case MFA_IS_READY:
		MLGD("MFA: MFA is ready");
		SET_FLAG(mid_info->event, MID_FLASH_START_EVT);
		mid_mfa_send_message(context, gr_ok, sizeof(gr_ok));
		break;
	case MFA_HAS_FINISHED:
		MLGD("MFA: MFA has finished");
		mid_mfa_send_message(context, gr_ok, sizeof(gr_ok));
		mid_mfa_stop(context, true);
		break;
	default:
		MLGW("MFA: Got unrecognized MID/MFA message from the MFA: %02x", message[1]);
		mid_mfa_send_message(context, gr_fail, sizeof(gr_fail));
		break;
	}

	return;
error_unrecognized:
error_invalid:
error_read:
error_nack:
	// Don't send if we've stopped the MFA already
	if (context->socket)
		mid_mfa_send_message(context, gr_fail, sizeof(gr_fail));
	return;
}


bool mid_mfa_start(struct mid_mfa* context)
{
	struct sockaddr_un addr;
	apr_status_t status;
	//apr_pollfd_t pollfd;
	bool connected = false;
	int retries_left = 10;
	int sock;

	MLGI("MFA: Starting the MFA");

#ifdef HAVE_ANDROID_OS
	if (property_set("ctl.start", context->service_name) < 0) {
		MLGE("MFA: Failed to request start of the MFA (possibly related to \"%s\")", strerror(errno));
		goto error_start;
	}
#else
	if (system(context->start_command) != 0) {
		MLGE("MFA: Failed to start the MFA");
		goto error_start;
	}
#endif
	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
	if (sock < 0) {
		MLGE("MFA: Failed to create socket for communication with the MFA: %s", strerror(errno));
		goto error_socket;
	}

	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, context->socket_path, sizeof(addr.sun_path));
	while (!connected) {
		if (retries_left == 0) {
			MLGE("MFA: Too many reconnect attempts. Giving up");
			goto error_connect;
		}

		MLGD("MFA: Connecting to the MFA via %s", addr.sun_path);
		if (connect(sock, (const struct sockaddr *)&addr, sizeof(addr)) >= 0) {
			connected = true;
		} else {
			MLGW("MFA: Failed to connect to the MFA via %s: %s", addr.sun_path, strerror(errno));
			sleep(1);
			retries_left--;
		}
	}

	context->socket = NULL;
	if ((status = apr_os_sock_put(&context->socket, &sock, context->pool)) != APR_SUCCESS) {
		char message[100];
		MLGE("MFA: Failed to create APR wrapper for MFA socket: %s", apr_strerror(status, message, sizeof(message)));
		goto error_apr;
	}

	// Not strictly necessary, as we only read after POLLIN, but it's
	// better to get an error message than a hang if something goes wrong.
	if ((status = apr_socket_opt_set(context->socket, APR_SO_NONBLOCK, 1)) != APR_SUCCESS) {
		char message[100];
		MLGE("MFA: Failed to make MFA socket non-blocking: %s", apr_strerror(status, message, sizeof(message)));
		goto error_apr;
	}

	MLGI("MFA: MID/MFA communication online");
	context->is_flashing = true;
	context->waiting_for_ack = false;
	context->was_successful = false;

	return true;

error_watch:
error_apr:
error_connect:
	close(sock);
error_socket:
#ifdef HAVE_ANDROID_OS
	if (property_set("ctl.stop", context->service_name) < 0) {
		MLGW("MFA: Failed to request to stop the MFA after unsuccessful statrup");
	}
#else
	if (!logging_system(context->stop_command)) {
		MLGW("MFA: Failed to stop the MFA after unsuccessful startup");
	}
#endif
error_start:
	return false;
}

bool mid_mfa_notify_on(struct mid_mfa* context)
{
	uint8_t message[4] = {MFA_MID_SYNC, MID_ENABLED_TX, 0, 0};
	bool ret;

	if (!context->is_flashing)
		return true;

	MLGD("MFA: Notifying the MFA that the modem is on");
	ret = mid_mfa_send_message(context, message, sizeof(message));
	if (ret)
		context->waiting_for_ack = true;
	return ret;
}

void mid_mfa_stop(struct mid_mfa* context, bool success)
{
	if (!context->is_flashing)
		return;

	MLGI("MFA: Shutting down communications with the MFA");
	SET_FLAG(context->mid_info->event, MID_FLASH_END_EVT);

	context->is_flashing = false;
	context->was_successful = success;
	if (context->socket != NULL) {
		//mid_eventloop_unwatch_socket(context->eventloop, context->socket);
		apr_socket_close(context->socket);
		context->socket = NULL;
	}
	MLGI("MFA: Stopping the MFA");
#ifdef HAVE_ANDROID_OS
	if (property_set("ctl.stop", context->service_name) < 0)
		MLGE("MFA: Failed to stop MFA service");
#else
	system(context->stop_command);
#endif
}

void mid_mfa_destroy(struct mid_mfa* context)
{
	if (context->is_flashing)
		mid_mfa_stop(context, false);
	apr_pool_destroy(context->pool);
}

bool mid_mfa_was_successful(struct mid_mfa* context)
{
	return context->was_successful;
}

bool mid_mfa_get_fd(struct mid_mfa* context, apr_pollfd_t* pollfd)
{
	if (!context->is_flashing)
		return false;

	pollfd->desc_type = APR_POLL_SOCKET;
	pollfd->desc.s = context->socket;
	pollfd->reqevents = APR_POLLIN;
	pollfd->p = context->pool;

	return true;
}
